#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <ctype.h>
extern int errno;

#include <io_common.h>

int IO_open ( IO_filehand* fh, char const* filename, char const* mode )
{
  int status = 0;
  char infoname[FILENAME_MAX + 1];

  /* reorder bytes; off by default for writes, file dep for reads */
  fh -> swap_bytes = 0;

  /* filename */
  strncpy ( fh -> filename, filename, FILENAME_MAX );

  /* access mode */
  strncpy ( fh -> mode, mode, IO_MODE_SIZE );

  /* open time */
  fh -> otime = time ( 0 );

  /* open the data file */
  fh -> data_errno = 0;
  fh -> data_fp = static_cast ( FILE*, fopen ( filename, mode ) );
  if ( fh -> data_fp == static_cast ( FILE*, 0 ) )
    {
      fh -> data_errno = errno;
      status += 1;
    }

  /* open the meta-data file */
  strncpy ( infoname, filename, FILENAME_MAX );
  strncat ( infoname, META_FILE_EXT, FILENAME_MAX );
  fh -> meta_errno = 0;
  fh -> meta_fp = static_cast ( FILE*, fopen ( infoname, mode ) );
  if ( fh -> meta_fp == static_cast ( FILE*, 0 ) )
    {
      fh -> meta_errno = errno;
      status += 2;
    }

  /* close files in case of error */
  if ( status )
    IO_close ( fh );

  return status;
}

int IO_close ( IO_filehand* fh )
{
  int status = 0;

  if ( fh -> data_fp ) status += fclose ( fh -> data_fp );
  if ( fh -> meta_fp ) status += fclose ( fh -> meta_fp );

  return status;
}

typedef struct { char c[2]; } swap16_t;
typedef struct { char c[4]; } swap32_t;
typedef struct { char c[8]; } swap64_t;

void IO_swap16 ( IO_type16 *v )
{
  swap16_t* w = static_cast ( swap16_t*, v );
  char tmp = w->c[0];
  w->c[0] = w->c[1];
  w->c[1] = tmp;
}

void IO_swap32 ( IO_type32 *v )
{
  swap32_t* w = static_cast ( swap32_t*, v );
  char tmp = w->c[0];
  w->c[0] = w->c[3];
  w->c[3] = tmp;
  tmp  = w->c[1];
  w->c[1] = w->c[2];
  w->c[2] = tmp;
}

void IO_swap64 ( IO_type64 *v )
{
  swap64_t* w = static_cast ( swap64_t*, v );
  char tmp = w->c[0];
  w->c[0] = w->c[7];
  w->c[7] = tmp;
  tmp  = w->c[1];
  w->c[1] = w->c[6];
  w->c[6] = tmp;
  tmp  = w->c[2];
  w->c[2] = w->c[5];
  w->c[5] = tmp;
  tmp  = w->c[3];
  w->c[3] = w->c[4];
  w->c[4] = tmp;
}

void IO_CRCsum32_init ( IO_checksum* check, int cycle29, int cycle31 )
{
  check -> cycle29 = cycle29;
  check -> cycle31 = cycle31;
  check -> sum29 = 0;
  check -> sum31 = 0;
  check -> word_count = 0;
}

void IO_CRCsum32 ( IO_type32u* buffer,
		   size_t buffer_bytes, IO_checksum* check )
{
  /* no remainder check; better be a whole number of 32-bit words! */
  /* could pad, but that fails for distributed data */
  size_t n_words = buffer_bytes / sizeof(IO_type32u);

  size_t cnt;
  for ( cnt = 0; cnt < n_words; ++cnt, ++buffer )
    {
      check -> sum29 ^=
	( ( *buffer ) << (      check -> cycle29 ) ) |
	( ( *buffer ) >> ( 32 - check -> cycle29 ) );

      check -> sum31 ^=
	( ( *buffer ) << (      check -> cycle31 ) ) |
	( ( *buffer ) >> ( 32 - check -> cycle31 ) );

      if ( ++(check -> cycle29) >= 29 ) check -> cycle29 = 0;
      if ( ++(check -> cycle31) >= 31 ) check -> cycle31 = 0;

    }
  check -> word_count += n_words;
}


int IO_write_meta_item ( IO_filehand* fh, IO_meta_item* entry )
{
  /* TODO: quote items that have embedded whitespace or newlines */

  fprintf ( fh -> meta_fp, "%s\t%s\n", entry -> key, entry -> value );

  return 0; /* meaningful status? */
}

IO_meta_item* IO_read_meta_item ( IO_filehand* fh )
{
  const char *white = " \t\r\n\f\v";
  char *p, buf[MAX_META_LINE+2]; /* ...\n\000 */
  int cnt, ret;
  int len;
  IO_meta_item* item;

  if ( fgets ( buf, MAX_META_LINE+1, fh -> meta_fp ) == 0 )
    {
      /* EOF or error */
      return ( static_cast ( IO_meta_item*, 0 ) );
    }

  len = strlen ( buf );
  if ( buf[len-1] != '\n' )
    {
      fprintf ( stderr,
	   "IO_read_meta_item: meta item line exceeds %d chars? truncating\n",
		MAX_META_LINE );

      /* flush past next newline */
      for ( ret = fgetc ( fh -> meta_fp );
	    ret != '\n' && ret != 0;
	    ret = fgetc ( fh -> meta_fp ) );
    }

  len--;
  buf[len] = 0; /* kill '\n'; null terminate */

  /* map whitespace to spaces */
  /*for ( cnt = 0; cnt < len; ++cnt )
    if ( isspace ( buf[cnt] ) ) buf[cnt] = ' ';*/

  /* allocate an item */
  item = static_cast ( IO_meta_item*, malloc ( sizeof( IO_meta_item ) ) );

  /* split key value on first whitespace */ /* TODO: quoted whitespace? */
  cnt = strcspn ( buf, white );

  /* copy key */
  item -> key = static_cast ( char*, malloc ( cnt + 1 ) );
  strncpy ( item -> key, buf, cnt ); item -> key[cnt] = 0;

  /* advance past whitespace */
  p = buf + cnt;
  p += strspn ( p, white );
  len = strlen ( p );

  /* copy value */
  item -> value = static_cast ( char*, malloc ( len + 1 ) );
  strncpy ( item -> value, p, len ); item -> value[len] = 0;

  return item;
}

/* use *ONLY* on dynamically allocated IO_meta_item's */
void IO_free_meta_item ( IO_meta_item* item )
{
  free ( item -> value );
  free ( item -> key );
  free ( item );
}

void IO_init_meta_list ( IO_meta_list* list )
{
  list -> length = 0;
  list -> head = 0;
  list -> tail = 0;
}

/* TODO: test!! */
IO_meta_item* new_item ( int sizeofkey, int sizeofval )
{
  int len;
  IO_meta_item *item;

  /* add space for nul char. and word align */
  ++sizeofkey;
  if ( sizeofkey % 2 )sizeofkey++;

  /* add space for nul char. and word align */
  ++sizeofval;
  if ( sizeofval % 2 )sizeofval++;

  len = sizeof(IO_meta_item) + sizeofkey + sizeofval;
  item = static_cast ( IO_meta_item*, malloc ( len ) );

  item -> key   = static_cast ( char*, item + 1 );
  item -> value = item -> key + sizeofkey;

  return item;
}

int IO_append_to_meta_list ( IO_meta_list* list,
			     IO_meta_item* item, int item_was_malloced )
{
  IO_meta_list_entry *new_entry;

  new_entry =
    static_cast ( IO_meta_list_entry*,
		  malloc ( sizeof(IO_meta_list_entry) ) );
  if ( new_entry == 0 )
    {
      fprintf ( stderr, "IO_append_to_meta_list: malloc failed\n" );
      return ( 1 );
    }

  new_entry -> item = item;
  new_entry -> item_was_malloced = item_was_malloced;
  new_entry -> next = 0;
  new_entry -> last = list -> tail;

  if ( list -> length )
    {
      list -> tail -> next = new_entry;
    }
  else
    {
      /* first entry */
      list -> head = new_entry;
    }

  list -> tail = new_entry;
  list -> length++;

  return ( 0 );
}

IO_meta_list_entry* IO_meta_first ( IO_meta_list* list )
{
  return list -> head;
}

IO_meta_list_entry* IO_meta_next ( IO_meta_list_entry* entry )
{
  return entry -> next;
}

int IO_meta_more ( IO_meta_list_entry* entry )
{
  return ( entry != 0 );
}

void IO_get_meta_list ( IO_filehand* fh, IO_meta_list* list )
{
  IO_meta_item* item;
  int item_was_malloced = 1;

  while ( ( item = IO_read_meta_item ( fh ) ) )
    {
      IO_append_to_meta_list ( list, item, item_was_malloced );
    }
}

int IO_put_meta_list ( IO_filehand* fh, IO_meta_list* list )
{
  int status;
  IO_meta_list_entry *ptr;

  for ( ptr = IO_meta_first ( list );
	IO_meta_more ( ptr );
	ptr = IO_meta_next ( ptr ) )
    {
      if ( ( status = IO_write_meta_item ( fh, ptr -> item ) ) )
	{
	  fprintf ( stderr, "meta-data write failed\n" );
	  return ( status );
	}
    }
  return ( 0 );
}

void IO_free_meta_list ( IO_meta_list* list )
{
  IO_meta_list_entry *ptr, *last;

  for ( ptr = list -> tail; ptr ; ptr = last )
    {
      last = ptr -> last;
      if ( ptr -> item_was_malloced )
	{
	  IO_free_meta_item ( ptr -> item );
	}
      free ( ptr );
    }
}
